using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using SkaldRogueLike.Main.CellContent;
using SkaldRogueLike.Main.CellContent.ContentFactories;
using SkaldRogueLike.Main.CellContent.Creatures;
using SkaldRogueLike.Main.CellContent.Items;
using SkaldRogueLike.Main.Common;
using SkaldRogueLike.Main.Constants.Enumerations;
using SkaldRogueLike.Main.DungeonGenerating;

namespace CatSkaldRoguelike.ConsoleGame
{
    public sealed class GameProcessor
    {
        const int ACode = 97;
        const int ZCode = 122;
        const int AllCode = 42;
        
        private readonly int _width;
        private readonly int _height;

        public bool End { get { return Character.HP <= 0; } }

        public CellContent[,] MapFrame { get; private set; }
        public CellContent[,] Map { get; private set; }

        public List<Creature> Creatures { get; private set; }
        public List<Item> Items { get; private set; }
        public Character Character { get; private set; }
        
        public GameProcessor()
        {
            var creator = new GameCreator(
                DungeonGenerator.CreateFinalMap(12, 6, 50, 50, 50, new RoomGenerator(2, 4, 2, 4, 2)));
            var mapSize = creator.GetFreeCells().Count();
            creator.CreateCellContent(Convert.ToInt32(mapSize * 0.04), new ItemFactory(60));
            creator.CreateCellContent(Convert.ToInt32(mapSize * 0.06), new MonsterFactory(90));
            creator.FillMapWithCellContent();

            Character = creator.Character;
            Creatures = creator.Creatures;
            Items = creator.Items;

            var mapPicture = creator.GenerateFilledMapPicture();
            _width = mapPicture.GetLength(0);
            _height = mapPicture.GetLength(1);
            MapFrame = GenerateMapFrame(mapPicture);
            
            Map = new CellContent[_width, _height];
            
            GenerateMap();
            UpdateMap();
        }

        private CellContent[,] GenerateMapFrame(Cell[,] mapPicture)
        {
            var result = new CellContent[_width, _height];
            for (var x = 0; x < _width; x++)
                for (var y = 0; y < _height; y++)
                {
                    result[x, y] = new CellContent
                    {
                        Image = mapPicture[x, y].Image,
                        Color = mapPicture[x, y].Color,
                    };
                }
            return result;
        }

        public string Update()
        {
            var text = MoveCreatures();
            UpdateMap();
            return text;
        }

        #region Creation
        public void UpdateMap()
        {
            GenerateMap();
            FillMap();
        }

        private void GenerateMap()
        {
            Map = new CellContent[_width,_height];
            Map = MapFrame;
        }

        private void FillMap()
        {
            foreach (var i in Items)
                Map[i.X, i.Y] = i;
            foreach (var c in Creatures.Where(cr => cr.HP > 0))
                Map[c.X, c.Y] = c;
            Map[Character.X, Character.Y] = Character;
        }

        #endregion

        #region MoveAndAct
        public string MoveCharacter(Direction way)
        {
            var sb = new StringBuilder();
            var movePoint = GetMovePoint(Character, way);
            if (IsWall(movePoint.X, movePoint.Y))
                return sb.Append(Messages.WallError).ToString();
            var cell = Map[movePoint.X, movePoint.Y];
            if (cell is Monster)
            {
                var monster = cell as Monster;
                sb.AppendFormat(Messages.HitPattern + " [{1}] dmg", monster.Name, Character.Attack(monster));
                if (monster.HP <= 0)
                    sb.AppendFormat(Messages.KillPattern, monster.Name);
            }
            else
            {
                Map[Character.X, Character.Y] = new CellContent();
                Character.Move(movePoint.X, movePoint.Y);
                Map[Character.X, Character.Y] = Character;
                var items = Items.Where(it => it.X == movePoint.X && it.Y == movePoint.Y).ToList();
                if (items.Count > 0)
                {
                    sb.AppendFormat(Messages.SeePattern, items.Aggregate("", (current, item) => current + ", " + item.Name));
                    sb.AppendFormat(Messages.PressToPattern, "space", "pick up");
                }
            }
            return sb.ToString();
        }

        private string MoveCreatures()
        {
            var sb = new StringBuilder();
            foreach (var creature in Creatures.Where(cr => cr.HP > 0).OfType<Monster>())
            {
                switch (creature.Condition)
                {
                    case Condition.Sleep:
                        sb.AppendFormat(Messages.WakeUpPattern, creature.Name);
                        creature.Condition = Condition.NotSeeYou;
                        break;
                    case Condition.NotSeeYou:
                        sb.AppendFormat(Messages.SeeYouPattern, creature.Name);
                        creature.Condition = Condition.None;
                        break;
                    case Condition.None:
                        var p = GetMovePoint(creature,
                            GetDirection(creature.X, creature.Y, Character.X, Character.Y));
                        var cell = Map[p.X, p.Y];
                        if (!(cell is Creature || IsWall(p.X, p.Y)))
                        {
                            Map[creature.X, creature.Y] = new CellContent();
                            creature.Move(p.X, p.Y);
                            Map[creature.X, creature.Y] = creature;
                        }
                        else if (Map[p.X, p.Y] is Character || (p.X == creature.X && p.Y == creature.Y))
                        {
                            sb.AppendFormat(Messages.GetDamagePattern + " [{1}] dmg", 
                                creature.Name, creature.Attack(Character));
                            if (Character.HP <= 0)
                                sb.AppendFormat(Messages.DiePattern, creature.Name);
                        }
                        break;
                    default:
                        break;
                }
                
            }
            return sb.ToString();
        }

        private static Direction GetDirection(int x1, int y1, int x2, int y2)
        {
            var dirX = Direction.None;
            Direction dirY;
            if (x1 - x2 > 1)
                dirX = Direction.West;
            else if (x1 - x2 < -1)
                dirX = Direction.East;
            if (y1 - y2 > 1)
            {
                dirY = Direction.North;
                if (dirX == Direction.East)
                    return Direction.NorthEast;
                if (dirX == Direction.West)
                    return Direction.NorthWest;
                return dirY;
            }
            if (y1 - y2 < -1)
            {
                dirY = Direction.South;
                if (dirX == Direction.East)
                    return Direction.SouthEast;
                if (dirX == Direction.West)
                    return Direction.SouthWest;
                return dirY;
            }
            return dirX;
        }
        
        public bool IsWall(int x, int y)
        {
            return MapFrame[x, y].Image == Styles.Wall;
        }

        private static Point GetMovePoint(CellContent cell, Direction way)
        {
            var result = new Point(cell.X, cell.Y);
            switch (way)
            {
                case Direction.North:
                    result.Y -= 1;
                    break;
                case Direction.NorthEast:
                    result.Y -= 1;
                    result.X += 1;
                    break;
                case Direction.East:
                    result.X += 1;
                    break;
                case Direction.SouthEast:
                    result.X += 1;
                    result.Y += 1;
                    break;
                case Direction.South:
                    result.Y += 1;
                    break;
                case Direction.SouthWest:
                    result.X -= 1;
                    result.Y += 1;
                    break;
                case Direction.West:
                    result.X -= 1;
                    break;
                case Direction.NorthWest:
                    result.X -= 1;
                    result.Y -= 1;
                    break;
            }
            return result;
        }
        #endregion

        #region Item processing
        public string ShowCurrentCellItems()
        {
            return GenerateItemList(
                new SortedListOfItems(Items.Where(it => it.X == Character.X && it.Y == Character.Y))
                );
        }

        public string ShowInventory()
        {
            return GenerateItemList(Character.GetItemsFromBag());
        }

        public string ShowEquipmentFromBag()
        {
            return GenerateItemList(Character.GetEquipmentFromBag());
        }

        public string ShowEquipped()
        {
            return GenerateItemList(Character.ShowEquipped());
        }

        public string PickUp(int i)
        {
            var items = Items.Where(it => it.X == Character.X && it.Y == Character.Y).OrderBy(it => it.Name).ToList();
            return items.Count > 0 
                ? string.Format(Messages.PickedUpPattern, Process(PickUpItem(), items, i)) 
                : Messages.Error;
        }

        public string Drop(int i)
        {
            var names = Process(DropItem(), Character.GetItemsFromBag(), i);
            var sb = new StringBuilder();
            sb.AppendFormat(Messages.DropPattern, names);
            sb.AppendFormat(Messages.SeePattern, names);
            sb.AppendFormat(Messages.PressToPattern, "space", "pick up");
            return sb.ToString();
        }

        public string Equip(int i)
        {
            var item = GetItemOrNull(Character.GetEquipmentFromBag().ToList<Item>(), i) as Equipment;
            if (item == null)
                return Messages.Error;
            Character.Equip(item);
            return string.Format(Messages.EquippedPattern, item.Name);
        }

        public string Unequip(int i)
        {
            var item = GetItemOrNull(Character.ShowEquipped().ToList<Item>(), i) as Equipment;
            if (item == null)
                return Messages.Error;
            Character.Unequip(item);
            return string.Format(Messages.UnequippedPattern, item.Name);
        }

        private static string GenerateItemList(IEnumerable<Item> all)
        {
            var sb = new StringBuilder();
            var i = ACode;
            foreach (var item in all)
            {
                sb.AppendFormat(Messages.ChooseFromListPattern, (char)i, item.Name);
                if (i >= ZCode) break;
                i++;
            }
            return sb.ToString();
        }

        private static Item GetItemOrNull(IList<Item> items, int i)
        {
            Item item;
            try
            {
                item = items[i - ACode];
            }
            catch (ArgumentOutOfRangeException)
            {
                return null;
            }
            return item;
        }

        private static string Process(Action<Item> action, IList<Item> items, int i)
        {
            var sb = new StringBuilder();
            if (i == AllCode)
            {
                foreach (var item in items)
                {
                    action(item);
                    sb.Append(item.Name);
                    sb.Append(",");
                }
                sb.Remove(sb.Length - 1, 1);
            }
            else
            {
                var item = GetItemOrNull(items, i);
                if (item == null)
                    return Messages.Error;
                action(item);
                sb.Append(item.Name);
            }
            return sb.ToString();
        }

        private Action<Item> PickUpItem()
        {
            return item =>
            {
                Character.PickUp(item);
                Items.Remove(item);
            };
        }

        private Action<Item> DropItem()
        {
            return item =>
            {
                Character.Drop(item);
                item.Move(Character.X, Character.Y);
                Items.Add(item);
            };
        }
        #endregion

        public string GetHelp()
        {
            return Messages.Help;
        }

        //todo
        public void Throw(Item item, CellContent cell)
        {
            Character.Drop(item);
            item.Move(cell.X, cell.Y);
            Items.Add(item);
        }
    }
}